package com.feinno.module.security.shiro.filter;

import java.util.Map;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.feinno.module.security.shiro.exception.InvaildCaptchaException;
import com.feinno.module.security.shiro.listener.ShireLoginLogoutSubject;
import com.google.common.collect.Maps;
import com.octo.captcha.service.CaptchaServiceException;
import com.octo.captcha.service.image.ImageCaptchaService;

/**
 * 为Form-POST认证扩展Captcha
 * 
 * @author zhangpu
 * 
 */

public class CaptchaFormAuthenticationFilter extends FormAuthenticationFilter {

	private static final Logger logger = LoggerFactory
			.getLogger(CaptchaFormAuthenticationFilter.class);

	/** 界面请求的Input-form表单名称 */
	public String captchaInputName = "captcha";

	/** 登录失败Redirect URL */
	private String failureUrl = "/login.jsp";

	/** 监听处理 */
	private ShireLoginLogoutSubject shireLoginLogoutSubject;

	/** Captcha验证服务 */
	@Autowired
	protected ImageCaptchaService imageCaptchaService;

	/**
	 * 扩展：在调用认证前，先验证验证码,同时，认证成功和失败都通过onLogin...方法直接Redirect到自定义的URL，进行后续日志，如日志拦截
	 * ，實現与安全控件解耦
	 */
	@Override
	protected boolean executeLogin(ServletRequest request,
			ServletResponse response) throws Exception {

		AuthenticationToken token = createToken(request, response);
		if (token == null) {
			String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken "
					+ "must be created in order to execute a login attempt.";
			throw new IllegalStateException(msg);
		}
		try {
			String requestCaptcha = request.getParameter(captchaInputName);
			checkCaptcha(requestCaptcha);
			logger.debug("Captcha validate success. token:[" + token + "]");
			Subject subject = getSubject(request, response);
			subject.login(token);
			shireLoginLogoutSubject.afterLogin(token, null,
					(HttpServletRequest) request,
					(HttpServletResponse) response);
			return onLoginSuccess(token, subject, request, response);
		} catch (AuthenticationException e) {
			logger.debug("login failure. token:[" + token + "], exception:["
					+ e.getClass().getName() + "]");
			shireLoginLogoutSubject.afterLogin(token, e,
					(HttpServletRequest) request,
					(HttpServletResponse) response);
			return onLoginFailure(token, e, request, response);
		}
	}

	protected void checkCaptcha(String requestCaptcha)
			throws InvaildCaptchaException {
		String captchaId = SecurityUtils.getSubject().getSession().getId()
				.toString();
		Boolean captchaPassed = Boolean.TRUE;
		try {
			captchaPassed = imageCaptchaService.validateResponseForID(
					captchaId, requestCaptcha);
		} catch (CaptchaServiceException e) {
			logger.debug("Validate captcha, Exception -- > InvaildCaptchaException");
			throw new InvaildCaptchaException("captchaId format invaild.");
		}
		logger.debug("Validate captcha, captchaPassed -- > " + captchaPassed);
		if (!captchaPassed) {
			throw new InvaildCaptchaException("captcha invaild.");
		}
	}

	/**
	 * 扩展登录失败回调
	 * 
	 * 实现：失败后直接Redirect到指定的失败处理界面，如果Redirect失败，则使用框架的实现，直接返回请求界面直接处理。
	 * 
	 * @param token
	 * @param e
	 * @param request
	 * @param response
	 * @return
	 */
	@Override
	protected boolean onLoginFailure(AuthenticationToken token,
			AuthenticationException e, ServletRequest request,
			ServletResponse response) {
		try {
			Map<String, String> queryParams = Maps.newHashMap();
			queryParams.put(getFailureKeyAttribute(), e.getClass().getName());
			WebUtils.issueRedirect(request, response, getFailureUrl(),
					queryParams, true);
			// login failed, redirect to failureUrl and appending error
			// retrun true express: the main filter does not continue execute.
			return true;
		} catch (Exception e2) {
			// if redirect failure, use original implement
			return super.onLoginFailure(token, e, request, response);
		}

	}

	/**
	 * 复写认证成功后，直接redirect到successUrl,不返回记录的上个请求地址。适应界面frame框架
	 */
	@Override
	protected void issueSuccessRedirect(ServletRequest request,
			ServletResponse response) throws Exception {
		WebUtils.issueRedirect(request, response, getSuccessUrl(), null, true);
	}

	public String getFailureUrl() {
		return failureUrl;
	}

	public void setFailureUrl(String failureUrl) {
		this.failureUrl = failureUrl;
	}

	public void setShireLoginLogoutSubject(
			ShireLoginLogoutSubject shireLoginLogoutSubject) {
		this.shireLoginLogoutSubject = shireLoginLogoutSubject;
	}

}
